home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / HTML / Menu.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  17.8 KB  |  610 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Ulf Wendel <ulf.wendel@phpdoc.de>                           |
  17. // |          Sebastian Bergmann <sb@sebastian-bergmann.de>               |
  18. // |          Alexey Borzov <avb@php.net>                                 |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Menu.php,v 1.14 2006/10/08 15:17:26 avb Exp $
  22. //
  23.  
  24. // Types of the menu entries, instead of former magic numbers
  25. define('HTML_MENU_ENTRY_INACTIVE',      0);
  26. define('HTML_MENU_ENTRY_ACTIVE',        1);
  27. define('HTML_MENU_ENTRY_ACTIVEPATH',    2);
  28. define('HTML_MENU_ENTRY_PREVIOUS',      3);
  29. define('HTML_MENU_ENTRY_NEXT',          4);
  30. define('HTML_MENU_ENTRY_UPPER',         5);
  31. define('HTML_MENU_ENTRY_BREADCRUMB',    6); // like activepath, but for 'urhere' type
  32.  
  33. /**
  34. * Generates a HTML menu from a multidimensional hash.
  35. *
  36. * Special thanks to the original author: Alex Vorobiev  <sasha@mathforum.com>.
  37. *
  38. * @version  $Revision: 1.14 $
  39. * @author   Ulf Wendel <ulf.wendel@phpdoc.de>
  40. * @author   Alexey Borzov <avb@php.net>
  41. * @access   public
  42. * @package  HTML_Menu
  43. */
  44. class HTML_Menu 
  45. {
  46.    /**
  47.     * Menu structure as a multidimensional hash.
  48.     * @var  array
  49.     * @see  setMenu(), Menu()
  50.     */
  51.     var $_menu = array();
  52.  
  53.    /**
  54.     * Mapping from URL to menu path.
  55.     * @var  array
  56.     * @see  getPath()
  57.     */
  58.     var $_urlMap = array();
  59.  
  60.    /**
  61.     * Path to the current menu item.
  62.     * @var  array
  63.     * @see  get(), getPath()
  64.     */
  65.     var $_path = array();
  66.  
  67.    /**
  68.     * Menu type: tree, rows, you-are-here.
  69.     * @var  array
  70.     * @see  setMenuType()
  71.     */
  72.     var $_menuType = 'tree';
  73.  
  74.    /**
  75.     * URL Environment Variable
  76.     * @var  string
  77.     */
  78.     var $_urlEnvVar = 'PHP_SELF';
  79.  
  80.    /**
  81.     * The URL to use an URL for the current page, instead of the one normally
  82.     * taken from env. variables
  83.     * @var string
  84.     * @see forceCurrentUrl(), getCurrentUrl()
  85.     */
  86.     var $_forcedUrl = '';
  87.  
  88.    /**
  89.     * Index of the menu item that should be made "current"
  90.     * @var mixed 
  91.     * @see forceCurrentIndex()
  92.     */
  93.     var $_forcedIndex = null;
  94.  
  95.    /**
  96.     * URL of the current page.
  97.     * @see  getCurrentURL(), getPath()
  98.     */
  99.     var $_currentUrl = '';
  100.  
  101.    /**
  102.     * The renderer being used to output the menu
  103.     * @var object HTML_Menu_Renderer
  104.     * @see render()
  105.     */
  106.     var $_renderer = null;
  107.  
  108.    /**
  109.     * Prefix for menu URLs 
  110.     * @var string
  111.     * @see setUrlPrefix()
  112.     */
  113.     var $_urlPrefix = '';
  114.  
  115.    /**
  116.     * Initializes the menu, sets the type and menu structure.
  117.     *
  118.     * @param    array   menu structure
  119.     * @param    string  menu type
  120.     * @param    string  env. variable used to determine current URL
  121.     * @see      setMenuType(), setMenu(), setURLEnvVar()
  122.     */
  123.     function HTML_Menu($menu = null, $type = 'tree', $urlEnvVar = 'PHP_SELF') 
  124.     {
  125.         if (is_array($menu)) {
  126.             $this->setMenu($menu);
  127.         }
  128.         $this->setMenuType($type);
  129.         $this->setURLEnvVar($urlEnvVar);
  130.     }
  131.  
  132.  
  133.    /**
  134.     * Sets the menu structure.
  135.     *
  136.     * The menu structure is defined by a multidimensional hash. This is
  137.     * quite "dirty" but simple and easy to traverse. An example
  138.     * show the structure. To get the following menu:
  139.     *
  140.     * 1  - Projects
  141.     * 11 - Projects => PHPDoc
  142.     * 12 - Projects => Forms
  143.     * 2  - Stuff
  144.     *
  145.     * you need the array:
  146.     *
  147.     * $menu = array(
  148.     *           1 => array(
  149.     *                  'title' => 'Projects',
  150.     *                  'url' => '/projects/index.php',
  151.     *                  'sub' => array(
  152.     *                           11 => array(
  153.     *                                       'title' => 'PHPDoc',
  154.     *                                       ...
  155.     *                                     ),
  156.     *                           12 => array( ... ),
  157.     *                 )
  158.     *             ),
  159.     *           2 => array( 'title' => 'Stuff', 'url' => '/stuff/index.php' )
  160.     *        )
  161.     *
  162.     * Note the index 'sub' and the nesting. Note also that 1, 11, 12, 2
  163.     * must be unique. The class uses them as ID's.
  164.     *
  165.     * @param    array
  166.     * @access   public
  167.     */
  168.     function setMenu($menu) 
  169.     {
  170.         $this->_menu   = $menu;
  171.         $this->_urlMap = array();
  172.     }
  173.  
  174.  
  175.    /**
  176.     * Sets the type of the menu.
  177.     * 
  178.     * Available types are: 'tree', 'rows', 'urhere', 'prevnext', 'sitemap'.
  179.     *
  180.     * @param    string type name
  181.     * @access   public
  182.     */
  183.     function setMenuType($menuType) 
  184.     {
  185.         $menuType = strtolower($menuType);
  186.         if (in_array($menuType, array('tree', 'rows', 'urhere', 'prevnext', 'sitemap'))) {
  187.             $this->_menuType = $menuType;
  188.         } else {
  189.             $this->_menuType = 'tree';
  190.         }
  191.     }
  192.  
  193.  
  194.    /**
  195.     * Sets the environment variable to use to get the current URL.
  196.     *
  197.     * @param    string  environment variable for current URL
  198.     * @access   public
  199.     */
  200.     function setURLEnvVar($urlEnvVar) 
  201.     {
  202.         $this->_urlEnvVar = $urlEnvVar;
  203.     }
  204.  
  205.  
  206.    /**
  207.     * Returns the HTML menu.
  208.     *
  209.     * @param    string  Menu type: tree, urhere, rows, prevnext, sitemap
  210.     * @return   string  HTML of the menu
  211.     * @access   public
  212.     * @see render()
  213.     */
  214.     function get($menuType = '') 
  215.     {
  216.         include_once 'HTML/Menu/DirectRenderer.php';
  217.         $renderer =& new HTML_Menu_DirectRenderer();
  218.         $this->render($renderer, $menuType);
  219.         return $renderer->toHtml();
  220.     }
  221.  
  222.  
  223.    /**
  224.     * Prints the HTML menu.
  225.     *
  226.     * @access   public
  227.     * @param    string  Menu type: tree, urhere, rows, prevnext, sitemap
  228.     * @see      get(), render()
  229.     */
  230.     function show($menuType = '') 
  231.     {
  232.         print $this->get($menuType);
  233.     }
  234.  
  235.  
  236.    /**
  237.     * Renders the menu.
  238.     *
  239.     * @access public
  240.     * @param  object HTML_Menu_Renderer  Renderer to use
  241.     * @param  string    type of the menu
  242.     * @throws PEAR_Error
  243.     */
  244.     function render(&$renderer, $menuType = '')
  245.     {
  246.         if ('' != $menuType) {
  247.             $this->setMenuType($menuType);
  248.         }
  249.         $this->_renderer =& $renderer;
  250.         // the renderer will throw an error if it is unable to process this menu type
  251.         $res = $this->_renderer->setMenuType($this->_menuType);
  252.         if (is_object($res) && is_a($res, 'PEAR_Error')) {
  253.             return $res;
  254.         }
  255.  
  256.         // storing to a class variable saves some recursion overhead
  257.         $this->_path = $this->getPath();
  258.  
  259.         switch ($this->_menuType) {
  260.             case 'rows': 
  261.                 $this->_renderRows($this->_menu);
  262.                 break;
  263.  
  264.             case 'prevnext': 
  265.                 $this->_renderPrevNext($this->_menu);
  266.                 break;
  267.  
  268.             case 'urhere':
  269.                 $this->_renderURHere($this->_menu);
  270.                 break;
  271.  
  272.             default:
  273.                 $this->_renderTree($this->_menu);
  274.         } // switch
  275.     }
  276.  
  277.  
  278.    /**
  279.     * Finds the type for the node.
  280.     * 
  281.     * @access private
  282.     * @param mixed   Node id
  283.     * @param string  Node 'url' attribute
  284.     * @param int     Level in the tree
  285.     * @return int    Node type (one of HTML_MENU_ENTRY_* constants)
  286.     */
  287.     function _findNodeType($nodeId, &$nodeUrl, $level)
  288.     {
  289.         $nodeUrl = $this->_prefixUrl($nodeUrl);
  290.         if ($this->_currentUrl == $nodeUrl) {
  291.             // menu item that fits to this url - 'active' menu item
  292.             return HTML_MENU_ENTRY_ACTIVE;
  293.         } elseif (isset($this->_path[$level]) && $this->_path[$level] == $nodeId) {
  294.             // processed menu item is part of the path to the active menu item
  295.             return 'urhere' == $this->_menuType? HTML_MENU_ENTRY_BREADCRUMB: HTML_MENU_ENTRY_ACTIVEPATH;
  296.         } else {
  297.             // not selected, not a part of the path to the active menu item
  298.             return HTML_MENU_ENTRY_INACTIVE;
  299.         }
  300.     }
  301.  
  302.  
  303.    /**
  304.     * Renders the tree menu ('tree' and 'sitemap')
  305.     * 
  306.     * @access private
  307.     * @param  array     (sub)menu being rendered
  308.     * @param  int       current depth in the tree structure
  309.     */
  310.     function _renderTree($menu, $level = 0)
  311.     {
  312.         foreach ($menu as $node_id => $node) {
  313.             $type = $this->_findNodeType($node_id, $node['url'], $level);
  314.  
  315.             $this->_renderer->renderEntry($node, $level, $type);
  316.             $this->_renderer->finishRow($level);
  317.  
  318.             // follow the subtree if the active menu item is in it or if we 
  319.             // want the full menu or if node expansion is forced (request #4391)
  320.             if (isset($node['sub']) && ('sitemap' == $this->_menuType || 
  321.                 HTML_MENU_ENTRY_INACTIVE != $type || !empty($node['forceExpand']))) {
  322.  
  323.                 $this->_renderTree($node['sub'], $level + 1);
  324.             }
  325.         }
  326.         $this->_renderer->finishLevel($level);
  327.         if (0 == $level) {
  328.             $this->_renderer->finishMenu($level);
  329.         }
  330.     }
  331.  
  332.  
  333.    /**
  334.     * Renders the 'urhere' menu
  335.     * 
  336.     * @access private
  337.     * @param  array     (sub)menu being rendered
  338.     * @param  int       current depth in the tree structure
  339.     */
  340.     function _renderURHere($menu, $level = 0)
  341.     {
  342.         foreach ($menu as $node_id => $node) {
  343.             $type = $this->_findNodeType($node_id, $node['url'], $level);
  344.  
  345.             if (HTML_MENU_ENTRY_INACTIVE != $type) {
  346.                 $this->_renderer->renderEntry($node, $level, $type);
  347.                 // follow the subtree if the active menu item is in it
  348.                 if (isset($node['sub'])) {
  349.                     $this->_renderURHere($node['sub'], $level + 1);
  350.                 }
  351.             }
  352.         }
  353.         if (0 == $level) {
  354.             $this->_renderer->finishRow($level);
  355.             $this->_renderer->finishMenu($level);
  356.         }
  357.     }
  358.  
  359.  
  360.    /**
  361.     * Renders the 'rows' menu
  362.     * 
  363.     * @access private
  364.     * @param  array     (sub)menu being rendered
  365.     * @param  int       current depth in the tree structure
  366.     */
  367.     function _renderRows($menu, $level = 0)
  368.     {
  369.         $submenu = false;
  370.  
  371.         foreach ($menu as $node_id => $node) {
  372.             $type = $this->_findNodeType($node_id, $node['url'], $level);
  373.  
  374.             $this->_renderer->renderEntry($node, $level, $type);
  375.  
  376.             // follow the subtree if the active menu item is in it
  377.             if (HTML_MENU_ENTRY_INACTIVE != $type && isset($node['sub'])) {
  378.                 $submenu = $node['sub'];
  379.             }
  380.         }
  381.  
  382.         // every (sub)menu has its own table
  383.         $this->_renderer->finishRow($level);
  384.         $this->_renderer->finishMenu($level);
  385.  
  386.         // go deeper if neccessary
  387.         if ($submenu) {
  388.             $this->_renderRows($submenu, $level + 1);
  389.         }
  390.     }
  391.  
  392.  
  393.    /**
  394.     * Renders the 'prevnext' menu
  395.     * 
  396.     * @access private
  397.     * @param  array     (sub)menu being rendered
  398.     * @param  int       current depth in the tree structure
  399.     * @param  int       flag indicating whether to finish processing
  400.     *                   (0 - continue, 1 - this is "next" node, 2 - stop)
  401.     */
  402.     function _renderPrevNext($menu, $level = 0, $flagStop = 0)
  403.     {
  404.         static $last_node = array(), $up_node = array();
  405.  
  406.         foreach ($menu as $node_id => $node) {
  407.             if (0 != $flagStop) {
  408.                 // add this item to the menu and stop recursion - (next >>) node
  409.                 if ($flagStop == 1) {
  410.                     $node['url'] = $this->_prefixUrl($node['url']);
  411.                     $this->_renderer->renderEntry($node, $level, HTML_MENU_ENTRY_NEXT);
  412.                     $flagStop = 2;
  413.                 }
  414.                 break;
  415.  
  416.             } else {
  417.                 $type = $this->_findNodeType($node_id, $node['url'], $level);
  418.                 if (HTML_MENU_ENTRY_ACTIVE == $type) {
  419.                     $flagStop = 1;
  420.  
  421.                     // WARNING: if there's no previous take the first menu entry - you might not like this rule!
  422.                     if (0 == count($last_node)) {
  423.                         reset($this->_menu);
  424.                         list($node_id, $last_node) = each($this->_menu);
  425.                         $last_node['url'] = $this->_prefixUrl($last_node['url']);
  426.                     }
  427.                     $this->_renderer->renderEntry($last_node, $level, HTML_MENU_ENTRY_PREVIOUS);
  428.  
  429.                     // WARNING: if there's no up take the first menu entry - you might not like this rule!
  430.                     if (0 == count($up_node)) {
  431.                         reset($this->_menu);
  432.                         list($node_id, $up_node) = each($this->_menu);
  433.                         $up_node['url'] = $this->_prefixUrl($up_node['url']);
  434.                     }
  435.                     $this->_renderer->renderEntry($up_node, $level, HTML_MENU_ENTRY_UPPER);
  436.                 }
  437.             }
  438.  
  439.             // remember the last (<< prev) node
  440.             $last_node = $node;
  441.  
  442.             if (isset($node['sub'])) {
  443.                 if (HTML_MENU_ENTRY_INACTIVE != $type) {
  444.                     $up_node = $node;
  445.                 }
  446.                 $flagStop = $this->_renderPrevNext($node['sub'], $level + 1, $flagStop);
  447.             }
  448.         }
  449.  
  450.         if (0 == $level) {
  451.             $this->_renderer->finishRow($level);
  452.             $this->_renderer->finishMenu($level);
  453.         }
  454.         return $flagStop;
  455.     }
  456.  
  457.  
  458.    /**
  459.     * Returns the path of the current page in the menu 'tree'.
  460.     *
  461.     * @return   array    path to the selected menu item
  462.     * @see      _buildUrlMap(), $_urlMap
  463.     */
  464.     function getPath() 
  465.     {
  466.         $this->_currentUrl = $this->getCurrentURL();
  467.         $this->_buildUrlMap($this->_menu, array());
  468.  
  469.         // If there is no match for the current URL, try to come up with
  470.         // the best approximation by shortening the url
  471.         while ($this->_currentUrl && !isset($this->_urlMap[$this->_currentUrl])) {
  472.             $this->_currentUrl = substr($this->_currentUrl, 0, -1);
  473.         }
  474.  
  475.         return isset($this->_urlMap[$this->_currentUrl])? $this->_urlMap[$this->_currentUrl]: array();
  476.     }
  477.  
  478.  
  479.    /**
  480.     * Builds the mappings from node url to the 'path' in the menu
  481.     *
  482.     * @access   private
  483.     * @param    array       (sub)menu being processed
  484.     * @param    array       path to the (sub)menu
  485.     * @return   boolean     true if the path to the current page was found, otherwise false.
  486.     * @see      getPath(), $_urlMap
  487.     */
  488.     function _buildUrlMap($menu, $path) 
  489.     {
  490.         foreach ($menu as $nodeId => $node) {
  491.             $url = $this->_prefixUrl($node['url']); 
  492.             $this->_urlMap[$url] = $path;
  493.  
  494.             if ($url == $this->_currentUrl) {
  495.                 return true;
  496.             }
  497.  
  498.             if (isset($node['sub']) && 
  499.                 $this->_buildUrlMap($node['sub'], array_merge($path, array($nodeId)))) {
  500.                 return true;
  501.             }
  502.         }
  503.         return false;
  504.     }
  505.  
  506.  
  507.    /**
  508.     * Returns the URL of the currently selected page.
  509.     *
  510.     * The returned string is used for all test against the URL's
  511.     * in the menu structure hash.
  512.     *
  513.     * @access public
  514.     * @return string
  515.     */
  516.     function getCurrentURL() 
  517.     {
  518.         if (!empty($this->_forcedUrl)) {
  519.             return $this->_forcedUrl;
  520.         } elseif (!empty($this->_forcedIndex)) {
  521.             return $this->_findUrlByIndex($this->_menu, $this->_forcedIndex);
  522.         } elseif (isset($_SERVER[$this->_urlEnvVar])) {
  523.             return $_SERVER[$this->_urlEnvVar];
  524.         } elseif (isset($GLOBALS[$this->_urlEnvVar])) {
  525.             return $GLOBALS[$this->_urlEnvVar];
  526.         } else {
  527.             return '';
  528.         }
  529.     }
  530.  
  531.  
  532.    /**
  533.     * Forces the given URL to be "current"
  534.     *
  535.     * @access public
  536.     * @param  string    Url to use
  537.     */
  538.     function forceCurrentUrl($url)
  539.     {
  540.         $this->_forcedUrl   = $url;
  541.         $this->_forcedIndex = null;
  542.     }
  543.  
  544.  
  545.    /**
  546.     * Sets the prefix for the URLs in the menu
  547.     * 
  548.     * @param  string
  549.     * @access public
  550.     */
  551.     function setUrlPrefix($prefix)
  552.     {
  553.         if (('' != $prefix) && ('/' != substr($prefix, -1))) {
  554.             $prefix .= '/';
  555.         }
  556.         $this->_urlPrefix = $prefix;
  557.     }
  558.  
  559.  
  560.    /**
  561.     * Adds the prefix to the URL (see request #2935)
  562.     *
  563.     * @access   private
  564.     * @param    string  URL
  565.     * @return   string  URL with prefix
  566.     * @see      setUrlPrefix()
  567.     */
  568.     function _prefixUrl($url)
  569.     {
  570.         return $this->_urlPrefix . ((empty($this->_urlPrefix) || '/' != $url{0})? $url: substr($url, 1));
  571.     }
  572.  
  573.  
  574.    /**
  575.     * Forces the menu item with the given index to become "current"
  576.     *
  577.     * Per request #3237
  578.     *
  579.     * @param    mixed   Menu item index
  580.     * @access   public
  581.     */
  582.     function forceCurrentIndex($index)
  583.     {
  584.         $this->_forcedIndex = $index;
  585.         $this->_forcedUrl   = ''; 
  586.     }
  587.  
  588.  
  589.    /**
  590.     * Returns the 'url' field of the menu item with the given index
  591.     *
  592.     * @param    array   Menu structure to search
  593.     * @param    mixed   Index
  594.     * @return   string  URL
  595.     * @access   private
  596.     */
  597.     function _findUrlByIndex(&$menu, $index)
  598.     {
  599.         foreach (array_keys($menu) as $key) {
  600.             if ($key == $index) {
  601.                 return $menu[$key]['url'];
  602.             } elseif (!empty($menu[$key]['sub']) && '' != ($url = $this->_findUrlByIndex($menu[$key]['sub'], $index))) {
  603.                 return $url;
  604.             }
  605.         }
  606.         return '';
  607.     }
  608. }
  609. ?>
  610.